/*******************************************************************
 * (c) Arphen Lin, arphenlin@gmail.com
 *
 * Required:
 * 	jQuery.js
 * 	
 * References:
 *  http://wiki.greasespot.net/Code_Snippets
 *	http://userscripts.org/forums/1/topics/48
 *
 ********************************************************************/
// ==UserScript==
// @name          GMLib
// @description   Greasemonkey Library 
// @version				2007-08-15
// @namespace
// @include       *
// @exclude
// ==/UserScript==

var GMLib = {
	Version: '0.1'
};

/*******************************************************************
 * String extension
 *******************************************************************/
String.prototype.trim = function(){
	return this.replace(/(^\s*)|(\s*$)/g, "");
};

String.prototype.triml = function(){
	return this.replace(/(^\s*)/g, "");
};

String.prototype.trimr = function(){
	return this.replace(/(\s*$)/g, "");
}; 

/*******************************************************************
 * Array extension
 * http://4umi.com/web/javascript/array.htm
 *******************************************************************/

// boolean = Array.once() - try until the callback function return true, or to the end of array
// callback: f(v, i, a){ return (true|false); }
Array.prototype.once = function( f ) {	
	for(var i=0; i<this.length; i++){
		if( f(this[i], i, this) ) return true; // done once
	}
	return false; // never done
};

// void Array.each(f) - invoke the callback function with each array elements
// callback: f(v, i, a){ return (true|false); }
Array.prototype.each = function( f ) {
	for(var i=0; i<this.length; i++){
		f(this[i], i, this);
	}
};

// Array.insert( index, value ) - Insert value at index of this array
Array.prototype.insert = function( i, v ) {
	if( i>=0 ) {		
		// eg. this = [0,1,2,3]
		var a = this.splice(i);	// eg. i=2, this = [0,1], a = [2,3]
		this[i++] = v;					// this = [0,1,v]
		for(var j=0; j<a.length; j++){
			this[i++] = a[j];
		}
	}
};

// i = Array.indexOf( value, begin, strict ) - Return index of the first element that matches value
Array.prototype.indexOf = function( v, b, s ) {
	b = +b || 0;
	for(var i=b; i<this.length; i++){
		if( this[i]===v || s && this[i]==v ) { return i; }
	}
	return -1;	
};

// i = Array.lastIndexOf( value, begin, strict ) - Return index of the last element that matches value
Array.prototype.lastIndexOf = function( v, b, s ) {
	b = +b || 0;
	var i = this.length; 
	while(i-->b) {
		if( this[i]===v || s && this[i]==v ) { return i; }
	}
	return -1;
};

// [i] = Array.random( range ) - Return a random element, optionally up to or from range
Array.prototype.random = function( r ) {
	var i = 0, l = this.length;
	if( !r ) { r = this.length; }
	else if( r > 0 ) { r = r % l; }
	else { i = r; r = l + r % l; }
	return this[ Math.floor( r * Math.random() - i ) ];
};

// Array.shuffle( deep ) - Randomly interchange elements of this array
Array.prototype.shuffle = function( b ) {
	var i = this.length, j, t;
	while( i ) {
		j = Math.floor( ( i-- ) * Math.random() );
		t = b && typeof this[i].shuffle!=='undefined' ? this[i].shuffle() : this[i];
		this[i] = this[j];
		this[j] = t;
	}
};

// new[] = Array.unique( strict ) - Remove duplicate values and return the new array
Array.prototype.unique = function( b ) {
	var a = [], i, l = this.length;
	for( i=0; i<l; i++ ) {
		if( a.indexOf( this[i], 0, b ) < 0 ) { a.push( this[i] ); }
	}
	return a;
};

// new[] = Array.walk() - Change each value according to a callback function, and return the new array
Array.prototype.walk = function( f ) {
	var a = [], i = this.length;
	while(i--) { a.push( f( this[i] ) ); }
	return a.reverse();
};

/*******************************************************************
 * Environment variables
 *******************************************************************/
function _getUrlParams(url){
	try{
		url = decodeURIComponent(url);
	}catch(e){
		//_log(url);
	}
	var s='';
	var i=url.indexOf('?');
	if(i>=0)	s = url.substring(i+1, url.length);

	var urlParams = {
		//'key1': 'value1',
		//'key2': 'value2',
		//...
		//'keyN': 'valueN'
	};

	// parameters
	if(s.length>0){
		var pairs = s.split('&');
		pairs.each(function(p){
			var ps = p.split('=');
			if(ps.length==2){
				urlParams[ps[0]] = ps[1];
			}
		});
	}

	return urlParams;
}

const THIS_URL = new String(window.location.href);
var THIS_URL_PARAMS = _getUrlParams(THIS_URL);	// eg. THIS_URL_PARAMS['key']


/*******************************************************************
 * Enumerations
 *******************************************************************/
const ENUM_ONOFF = {
	OFF: 0,
	ON: 1
};

/*******************************************************************
 * _log 
 *******************************************************************/
const ENUM_GM_LOG = {
  INFO: 0,
	WARN: 1,
	ERR: 2
};

function _log(message, type){
	//GM_log(message, (type? type : ENUM_GM_LOG.INFO) );
	gmConsole.log(message);
}

/*******************************************************************
 * serialize/deserialize objects
 *******************************************************************/ 
// example:
// var settings = {a: 1, b: 2, c: 3};
// _serialize('test', settings);
// var _settings = _deserialize('test'); 
// now "settings == _settings" should be true
function _deserialize(name, def) {
	//return eval(GM_getValue(name, (def ? def : '({})')) );
}

function _serialize(name, val) {
	//GM_setValue(name, uneval(val));
}

/*******************************************************************
 * DOM
 *******************************************************************/

// get element by id
function _(e) { return document.getElementById(e); }	

// get elements by a tagname
function _t() { // [obj], tag_name
	switch(arguments.length){
		case 1:
			return document.getElementsByTagName(arguments[0]); 
		case 2:
			return arguments[0].getElementsByTagName(arguments[1]);
		default:
			return null;
	}
}

// get elements by tagnames
function _ts() { // _ts('h1,h2,h3'); _ts(obj, 'a,span,textarea');
	var obj=null, list='';
	switch(arguments.length){
		case 1:
			obj = document;
			list = arguments[0];
			break;
		case 2:
			obj = arguments[0];			
			list = arguments[1];			
			break;
		default:
			return null;
	}

  var tagNames = list.split(',');
  var resultArray = new Array();
  for (var i=0;i<tagNames.length;i++)
  {
    var tags = obj.getElementsByTagName(tagNames[i]);
    for (var j=0;j<tags.length;j++)
    {
      resultArray.push(tags[j]);
    }
  }
  var testNode = resultArray[0];
  if (testNode.sourceIndex)
  {
    resultArray.sort(function (a,b) {
        return a.sourceIndex - b.sourceIndex;
    });
  }
  else if (testNode.compareDocumentPosition)
  {
    resultArray.sort(function (a,b) {
        return 3 - (a.compareDocumentPosition(b) & 6);
    });
  }
  return resultArray;
}

// create element by tag name
function _ce(e) { return document.createElement(e); }

// create text node
function _ct(e) { return document.createTextNode(e); }

// remove element, eg. _del(obj); _del($x("//a"));
function _del(e) {
	if(e.length == undefined){	// single object
		e.parentNode.removeChild(e);
	}else{	// array
		e.each(function(ei){
			ei.parentNode.removeChild(ei);
		});
	}
}

// append element
function _add() {	// [parent], node
	switch(arguments.length){
		case 1:
			document.body.appendChild(arguments[0]); 
			break;
		case 2:
			arguments[0].appendChild(arguments[1]); 
			break;
	}	
}

// insert element before ...
function _insert(newNode, node){	
	node.parentNode.insertBefore(newNode, node); 
}

// insert element after ...
function _insertAfter(newNode, node){
	if(node.nextSibling)
		node.parentNode.insertBefore(newNode, node.nextSibling); 
	else
		_add(node.parentNode, newNode);
}

// exchange 2 elements
function _exchange(obj1, obj2){
	var obj1Tmp = obj1.cloneNode(true);
	var obj2Tmp = obj2.cloneNode(true);
	obj1.parentNode.replaceChild(obj2Tmp, obj1);
	obj2.parentNode.replaceChild(obj1Tmp, obj2);	
}

// replace element with another
function _replace(newObj, oldObj) {
	oldObj.parentNode.replaceChild(newObj, oldObj);
}


// insert CSS
// Ex: _css('.date {font-size: 50pt !important;}');
function _css(css) {
	var head = _t('head')[0];
	if(!head) { return; }
	var style = _ce('style');
	style.type = 'text/css';
	style.innerHTML = css;
	head.appendChild(style);
}

// xpath to get object list
function $x(xpath, root){	// root: optional
	var doc = root ? (root.evaluate? root: root.ownerDocument): document;
  var snap = doc.evaluate( xpath, root||doc, null, null, null );
  var result = [];
	while(true){
		var nextItem = snap.iterateNext();
		if(nextItem)
			result.push(nextItem);
		else
			return result;
	}
}

// xpath to get single(first) object
function $x0(xpath, root) {
	var objs = $x(xpath, root);
	return (objs.length>0? objs[0]: null);
}

// dump an object
const MAX_DUMP_DEPTH = 10;
function _dumpObj(obj, name, indent, depth) {
	if (depth > MAX_DUMP_DEPTH) {
		return indent + name + ": <Maximum Depth Reached>\n";
	}
	if (typeof obj == "object") {
		var child = null;
		var output = indent + name + "\n";
		indent += "\t";
		for (var item in obj)
		{
			try {
				child = obj[item];
			} catch (e) {
				child = "<Unable to Evaluate>";
			}
			if (typeof child == "object") {
				output += _dumpObj(child, item, indent, depth + 1);
			} else {
				output += indent + item + ": " + child + "\n";
			}
		}
		return output;
	} else {
		return obj;
	}
}

function _styleToString(style) {
	if(!style)	return '';

	var output = '';
	for (var item in style)
	{
		if(item=='length')	continue;
		if(isNaN(parseInt(item)))
			output += (output.length==0?'':'&') + item + "=" + style[item];
	}
	
	return output;
}

// format error message
function _err(e){
	// get error line number and re-throw it 
	if(e.stack){
		var r = e.stack.match(/:[\d]+/gi);	
		if(r){
			e.lineNumber -= parseInt(r[r.length-1].replace(':',''));
		}
	}
	e.lineNumber = 'Line: ' + e.lineNumber;	
	e.message = '[' + e.name + '] ' + e.message;
	e.fileName = null;	
	throw(e);	
}



/*******************************************************************
 * HTML
 *******************************************************************/

function _unifyClass(obj){
	if(!obj.className)	return null;	
	
	var cs = obj.className.trim().replace(/\s+/, ' ').split(' ').unique();
	obj.className = cs.join(' ');
	return cs;
}

function _hasClass(obj, c){
	if(!obj.className)	return false;	
	
	var cs = _unifyClass(obj);
	return (cs.indexOf(c)>=0);
}

function _delClass(obj, c){
	if(_hasClass(obj, c)){
		var cs = _unifyClass(obj);
		var i = cs.indexOf(c);
		cs.splice(i,1);
		obj.className = cs.join(' ');		
	}	
}

function _addClass(obj, c){
	if(!_hasClass(obj, c)){
		if(obj.className){
			obj.className += ' ' + c;
		}else{
			obj.className = c;
		}
	}
}

function _submitForm(f){
	// simulate a button-click action 
	var b = _ce('button');
	b.style.display = 'none';
	f.appendChild(b);
	b.click();
}

function _htmlAddColor(text, color){
	return('<span style="font-weight: bold; color:'+color+'">'+text+'</span>');
}

function _htmlSetTitle(s){
	document.title = s;
}

// DOM misc =====================================================================================================
const OBJ_ID_HIDDEN_DIV = 'OBJ_ID_HIDDEN_DIV';
function _domInsertHddenHtml(html){
  var div = _(OBJ_ID_HIDDEN_DIV);
  if (!div) {
		div = _ce('div');
		document.body.appendChild(div);
		div.id = OBJ_ID_HIDDEN_DIV;
		div.style.display = 'none';
  }
	
	div.innerHTML = html;	
	return div;
}


function _domCreateImgButton(info){	//{id, url, icon, target, title, func}
	var A_CLASS_BUTTON = 'linkButton';
	var iconButton = _ce('a');
	iconButton.className = A_CLASS_BUTTON;
	
	iconButton.innerHTML = '<img border=0 src="' + info.img +'">';	
	
	if(info.id) iconButton.id = info.id;
	if(info.url) iconButton.href = info.url;
	if(info.target) iconButton.target = info.target;
	if(info.hint) iconButton.title = info.hint;
	
	if(info.attrs){
		info.attrs.each(function(attr){
			iconButton.setAttribute(attr[0], attr[1]);
		});
	}

	if(info.events){
		info.events.each(function(e){
			iconButton.addEventListener(e[0], e[1], true);
		});
	}
	
	return iconButton;
}



// Ajax =====================================================================================================

function _ajax(url, callback, userData){	// callback: function(r){}; userData: {}
	// check domain
	var host = document.location.host;
	if(url.indexOf(host)>0){
		_ajax_xmlhttpreq(url, callback, userData);
	}else{
		_ajax_iframe(url, callback, userData);
	}
}

function _ajax_xmlhttpreq(url, callback, userData){	// callback: function(r){}; userData: {}
	function switchAjaxHint(msg, onoff){
		var OBJ_ID_AJAX_HINT = 'aplAjaxHint';
		var div = _(OBJ_ID_AJAX_HINT);
		if(!div){
			div = _ce('div');
			div.id = OBJ_ID_AJAX_HINT;
			div.style.display = 'none';
			document.body.appendChild(div);
		}
		
		if(onoff == ENUM_ONOFF.ON){
			div.innerHTML += '<nobr>'+msg+'</nobr><br>';
			div.style.display = 'block';	
		}else{
			var nobrs = $x("//div[@id='"+OBJ_ID_AJAX_HINT+"']/nobr");
			// for(var i=0; i<nobrs.length; i++){
				// var nobr = nobrs[i];
				// if(nobr.innerHTML.indexOf(msg)>=0){
					// var br = nobr.nextSibling;
					// br.parentNode.removeChild(br);
					// nobr.parentNode.removeChild(nobr);
					// break;
				// }
			// }
			nobrs.once(function(nobr){
				if(nobr.innerHTML.indexOf(msg)>=0){
					var br = nobr.nextSibling;
					br.parentNode.removeChild(br);
					nobr.parentNode.removeChild(nobr);
					return true;
				}else{
					return false;
				}
			});
			nobrs = $x("//div[@id='"+OBJ_ID_AJAX_HINT+"']/nobr");
			if(nobrs.length==0)	div.style.display = 'none';	
		}
	}

	// append hash code
	var hash = _hash();
	url += (url.indexOf('?')>0? '&':'?') + 'hash=' + hash;
		
	try{
		switchAjaxHint(url, ENUM_ONOFF.ON);		
		// replace with prototype's Ajax.Request()
		
		try{
			new Ajax.Request(
				url, 
				{
					method: 'get',
					onSuccess: function(r){
						try{
							switchAjaxHint(hash, ENUM_ONOFF.OFF);
							callback(r, userData);
						}catch(e){
							_log(e);
						}
					}
				}
			);
		}catch(e){
			_log(e);
		}
		
		_log('_ajax ok');
		
	}catch(e){
		_log('_ajax(): '+url, ENUM_GM_LOG.ERR);
	}
}

// implement ajax using iframe
function _switchAjaxHint(msg, onoff){
	var OBJ_ID_AJAX_HINT = 'aplAjaxHint';
	var div = _(OBJ_ID_AJAX_HINT);
	if(!div){
		div = _ce('div');
		div.id = OBJ_ID_AJAX_HINT;
		div.style.display = 'none';
		document.body.appendChild(div);
	}
	
	if(onoff == ENUM_ONOFF.ON){
		div.innerHTML += '<nobr>'+msg+'</nobr><br>';
		div.style.display = 'block';	
	}else{
		var nobrs = $x("//div[@id='"+OBJ_ID_AJAX_HINT+"']/nobr");
		// for(var i=0; i<nobrs.length; i++){
			// var nobr = nobrs[i];
			// if(nobr.innerHTML.indexOf(msg)>=0){
				// var br = nobr.nextSibling;
				// br.parentNode.removeChild(br);
				// nobr.parentNode.removeChild(nobr);
				// break;
			// }
		// }
		nobrs.once(function(nobr){
			if(nobr.innerHTML.indexOf(msg)>=0){
				var br = nobr.nextSibling;
				br.parentNode.removeChild(br);
				nobr.parentNode.removeChild(nobr);
				return true;
			}else{
				return false;
			}
		});
		nobrs = $x("//div[@id='"+OBJ_ID_AJAX_HINT+"']/nobr");
		if(nobrs.length==0)	div.style.display = 'none';	
	}
}

function _ajax_iframe(url, callback, userData){	// callback: function(r){}; userData: {}
	
	// append hash code
	var hash = _hash();
	url += (url.indexOf('?')>0? '&':'?') + 'hash=' + hash;
		
	try{
		_switchAjaxHint(url, ENUM_ONOFF.ON);
		
		var iframe = _ce('iframe');
		_add(iframe);
		iframe.id = 'iframe_' + hash;
		iframe.hash = hash;
		iframe.setAttribute('style', 'display: none; visibility: hidden; height: 1px;');
		
		iframe.addEventListener('load', function(e){
			var ifm = this;
			_log('iframe.onload(): ' + ifm.url);
			_switchAjaxHint(ifm.hash, ENUM_ONOFF.OFF);
			var responseText = _extractIFrameBody(ifm).innerHTML;
			_del(ifm);
			callback({
				'responseText': responseText
			}, userData);
		}, false);
		
		iframe.src = url;
		
	}catch(e){
		_log('_ajax(): '+url, ENUM_GM_LOG.ERR);
	}
}

function _extractIFrameBody(iFrameEl) { 
	var doc = null; 
	if (iFrameEl.contentDocument) { 
		// For NS6 
		doc = iFrameEl.contentDocument; 
	} else if (iFrameEl.contentWindow) { 
		// For IE5.5 and IE6 
		doc = iFrameEl.contentWindow.document; 
	} else if (iFrameEl.document) { 
		// For IE5 
		doc = iFrameEl.document; 
	} else { 
		alert("Error: could not find sumiFrame document"); 
		return null; 
	} 
	
	return doc.body; 
} 


function _getAjaxHtmlBody(r){	// r: ajax response object
	var s = new String(r.responseText);
	s = s.replace(/\n/g, ' ');
	s = s.replace(/^.*<body[^>]*>(.*)<\/body>.*$/gi, '$1');	// get data inside <body>...</body>
	return s;
}

// Mozilla Only =====================================================================================
/* [].each(function(v,i,a){ //to do });
var a = [1,2,3];
a.each(function(v,i,a){
  print(i);
  print(v);
  print(a);
});
*/

// load javascript dynamically =====================================================================
function _loadJS(jsUrl){
	//<script src="/javascripts/ebnf.js" type="text/javascript" language="JavaScript"></script>
	var s = _ce('script');
	s.setAttribute('type', 'text/javascript');
	s.setAttribute('language', 'JavaScript');
	s.setAttribute('src', jsUrl);	
	_add($x0("//head"), s);
}

// insert javascript code 
function _insertJS(js_code){
	var s = _ce('script');
	s.setAttribute('type', 'text/javascript');
	s.innerHTML = js_code; // eg. function(x){...}
	_add($x0("//head"), s);	
}

function _loadCSS(cssUrl){
	//<link href="/stylesheets/main.css" rel="stylesheet" type="text/css" />
	var s = _ce('link');
	s.setAttribute('type', 'text/css');
	s.setAttribute('rel', 'stylesheet');
	s.setAttribute('href', cssUrl);	
	_add($x0("//head"), s);	
}

function _hash(len){
	if(!len) len=20;
	return Math.random().toString().substring(2, len);
}



// ============================================================
// myDragdropper
// (Usage) the object should have 'movable' class name
// ============================================================
const CLASS_MOVABLE = 'CLASS_MOVABLE';
const CLASS_MOVING = 'CLASS_MOVING';
var myDragdropper = {

	selObj: null,
	orgCursor: null,   // The original Cursor (mouse) Style so we can restore it
	dragOK: false,    // True if we're allowed to move the element under mouse
	dragXoffset: 0,    // How much we've moved the element on the horozontal
	dragYoffset: 0,    // How much we've moved the element on the verticle

	moveHandler: function(e){
		//_log('moveHandler');
		if (e == null) { e = window.event }
		if (e.button<=1&&this.dragOK){
			this.selObj.style.left=e.clientX-this.dragXoffset+'px';
			this.selObj.style.top=e.clientY-this.dragYoffset+'px';
			return false;
		}
	},

	cleanup: function(e) {
		//_log('cleanup');
		//document.onmousemove=null;
		//document.onmouseup=null;
		//document.removeEventListener('mousemove', myDragdropper.moveHandler, true);
		//document.removeEventListener('mouseup', myDragdropper.cleanup, true);		
		this.selObj.removeEventListener('mousemove', myDragdropper.moveHandler, true);
		this.selObj.removeEventListener('mouseup', myDragdropper.cleanup, true);		

		//this.selObj.style.cursor=this.orgCursor;
		this.selObj.style.cursor='move';
		this.dragOK=false;
		_delClass(this, CLASS_MOVING);
	},

	dragHandler: function(e){		
		//_log('dragHandler');
		var htype='-moz-grabbing';
		if (e == null) { e = window.event; htype='move';}
		var target = e.target != null ? e.target : e.srcElement;
		this.selObj=target;
		this.orgCursor=target.style.cursor;		
		
		//if (target.className.search(CLASS_MOVABLE)>=0) {
		if(_hasClass(target, CLASS_MOVABLE)){
			_addClass(target, CLASS_MOVING);
			target.style.cursor=htype;
			this.dragOK=true;
			this.dragXoffset=e.clientX-parseInt(this.selObj.style.left);
			this.dragYoffset=e.clientY-parseInt(this.selObj.style.top);
			//document.onmousemove=myDragdropper.moveHandler;
			//document.onmouseup=myDragdropper.cleanup;
			//document.addEventListener('mousemove', myDragdropper.moveHandler, true);
			//document.addEventListener('mouseup', myDragdropper.cleanup, true);
			this.selObj.addEventListener('mousemove', myDragdropper.moveHandler, true);
			this.selObj.addEventListener('mouseup', myDragdropper.cleanup, true);
			return false;
		}
	},
	
	init: function(){
		//document.onmousedown=myDragdropper.dragHandler;
		//document.addEventListener('mousedown', myDragdropper.dragHandler, true);
	},
	
	close: function(){
		//document.removeEventListener('mousedown', myDragdropper.dragHandler, true);
	},
	
	add: function(obj){
		_addClass(obj, CLASS_MOVABLE);
		obj.style.cursor = 'move';
		obj.addEventListener('mousedown', myDragdropper.dragHandler, true);
	},
	
	remove: function(obj){
		_delClass(obj, CLASS_MOVABLE);
		obj.removeEventListener('mousedown', myDragdropper.dragHandler, true);
	}
};

//myDragdropper.init();

// =====================================================================================================
// gmConsole
// (Usage) gmConsole.init(); 
// =====================================================================================================
const OBJ_ID_CONSOLE_WRAPPER = 'OBJ_ID_CONSOLE_WRAPPER';
const OBJ_ID_CONSOLE_TOOLBAR = 'OBJ_ID_CONSOLE_TOOLBAR';
const OBJ_ID_CONSOLE = 'OBJ_ID_CONSOLE';

function Console(id){
	// ************************************************************************ 
	// PUBLIC PROPERTIES -- ANYONE MAY READ/WRITE 
	// ************************************************************************ 
	this.id=id;

	// ************************************************************************ 
	// PRIVATE VARIABLES AND FUNCTIONS 
	// ONLY PRIVELEGED METHODS MAY VIEW/EDIT/INVOKE 
	// *********************************************************************** 
	var _consoleWrapper;
	var _console;
	var _counter=0;
	
	function _formatText(msg){	
		return '<p class="row'+(_counter++ % 2)+'">' + msg + '</p>';		
	}
	
	function _log(msg, clear){
		if(_console)
			_console.innerHTML = _formatText(msg) + (clear? '': _console.innerHTML);
	}
	// ************************************************************************ 
	// PRIVILEGED METHODS 
	// MAY BE INVOKED PUBLICLY AND MAY ACCESS PRIVATE ITEMS 
	// MAY NOT BE CHANGED; MAY BE REPLACED WITH PUBLIC FLAVORS 
	// ************************************************************************ 	
	this.init = function(id){	
		if(id) this.id = id;
		
		if(_console) return;
		
		var div = _ce('div');
		div.id = OBJ_ID_CONSOLE_WRAPPER;
		div.setAttribute('style', 
			'position: absolute; z-index: 999; top: 100px; left: 100px;'
		);
		
		div.innerHTML = this.id +
		'<div id="'+OBJ_ID_CONSOLE_TOOLBAR+'"></div>'+
		'<div id="'+OBJ_ID_CONSOLE+'"></div>';

		document.body.appendChild(div);
		
		_consoleWrapper = _(OBJ_ID_CONSOLE_WRAPPER);		
		_console = _(OBJ_ID_CONSOLE);
		var toolbar = _(OBJ_ID_CONSOLE_TOOLBAR);
		
		var a = _ce('a');		
		a.href = '#';
		a.innerHTML = 'Clear';
		a.addEventListener('click', this.clear, true);
		_add(toolbar, a);
		
		a = _ce('a');		
		a.href = '#';
		a.innerHTML = 'OnOff';
		a.addEventListener('click', this.onoff, true);
		_add(toolbar, a);
		
		a = _ce('a');
		a.href = '#';
		a.innerHTML = 'Test';
		a.addEventListener('click', this.logTest, true);
		_add(toolbar, a);
				
		a = _ce('a');
		a.href = '#';
		a.innerHTML = 'Exit';
		a.addEventListener('click', this.close, true);
		_add(toolbar, a);

		// make it movable
		myDragdropper.add(_consoleWrapper);
	};
	
	this.onoff = function(){
		_console.style.display = _console.style.display=='none' ? 'block': 'none';
	};
	
	this.log = function(msg, clear){
		_log(msg, clear);
	};
	
	this.logTest = function(){
		_log('logTest');
	};
	
	this.clear = function(){
		_counter = 0;
		_console.innerHTML = '';			
	};
	
	this.close = function(){
		_consoleWrapper.style.display = 'none';
	};
	
}

// ************************************************************************ 
// PUBLIC METHODS -- ANYONE MAY READ/WRITE 
// ************************************************************************ 
Console.prototype.getId = function(){ 
	return this.id;
};

Console.prototype.setId = function(id){ 
	this.id = id;
};

var gmConsole = new Console('gmConsole');

// =====================================================================================================
// Unit test
// =====================================================================================================
var GMLibTest = {
	run: function(){
		gmConsole.init('GMLib Unit Test');
		
		// String
		var s = ' This is a string. ';
		_log('s = "'+s+'"');
		_log('s.triml() = "'+s.triml()+'"');
		_log('s.trimr() = "'+s.trimr()+'"');
		_log('s.trim() = "'+s.trim()+'"');
		
		// Array
		var a = [
			'This',
			'is',
			'a',
			'string'
		];
		_log('a[] = ['+a+']');
				
		_log('a.lastIndexOf("is") = '+a.lastIndexOf("is"));
		
		_log('a.random() = ' + a.random());
		
		a.shuffle();
		_log('a.shuffle() = [' + a + ']');
				
		a.insert(2, "not");
		_log('a.insert(2, "not") = ['+ a +']');
		
		var b = a.walk(function(v){
			return '('+v+')';
		});
		_log('b = a.walk(f) = [' + b + ']');
		
		b = a.slice();
		b.push('is');
		_log('b[] = ['+b+']');
		
		var a = b.unique();
		_log('a = b.unique() = [' + a + ']');
		
		a.each(function(v,i){
			_log('a.each(f): a['+i+'] = ' + v);
		});

		a.once(function(v){
			_log('a.once(f(lookfor_a)): ' + v + (v=='a'? '*': ''));
			return (v=='a');
		});
		
		// className
		a = { className: ' classA  classB classC '	};
		if(_hasClass(a, 'classC')){
			_delClass(a, 'classC');
		}
		_addClass(a, 'classC');
		_log('a.className = "' + a.className + '"');
		
		// THIS_URL
		_log(THIS_URL_PARAMS.a);
		_log(THIS_URL_PARAMS.b);
		_log(THIS_URL_PARAMS.c);
		
	}
};

//GMLibTest.run();

